package jaux.tank.generator.template;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.beetl.core.Template;

import jaux.tank.generator.domain.TankApplication;
import jaux.tank.generator.domain.TankEntity;
import jaux.tank.generator.domain.TankRoot;
import jaux.tank.generator.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TankApplicationTemplate {
	
	private static final String SERVER_TEMPLATE_ROOT = "server";
	private static final String WEB_TEMPLATE_ROOT = "web";
	private static final String SERVER_ROOT = "server";
	private static final String WEB_ROOT = "web";
	
	public static final String TEMPLATE_FILE_EXTENSION = ".btl";
	
	private static TankApplicationTemplate instance = null;
	
	
	public static TankApplicationTemplate getInstance() {
		if(instance == null) {
			instance = new TankApplicationTemplate();
		}
		return instance;
	}
	
	private TankApplicationTemplate() {
	}
		
	
	
	public  void renderServer(TankApplication tankApplication,TankRoot tankRoot, File outDir, boolean overwrite) throws IOException, URISyntaxException {
		// get all template files
		URL originTemplatesDir = ClassLoader.getSystemResource(TankTemplate.TANK_TEMPLATE_ROOT+"/"+SERVER_TEMPLATE_ROOT);
		Path sourcePath = Path.of(originTemplatesDir.toURI());
		Collection<File> templateFiles = FileUtils.listFiles(sourcePath.toFile(), null, true);
		// create temp source dir
		File tempSourceDir = Files.createTempDirectory("temp_tank_server_source").toFile();
		tempSourceDir.deleteOnExit();
		// render
		for(File templateFile: templateFiles) {
			Path templatePath = sourcePath.relativize( templateFile.toPath());
			String templateName = SERVER_ROOT+File.separator+templatePath.toString();
			Template targetPathTemplate = TankTemplate.getInstance().getStringTemplate(templateName);
			Template targetFileNameTemplate = TankTemplate.getInstance().getStringTemplate(templateFile.getName());
			bindTemplate(tankApplication, tankRoot, targetPathTemplate, targetFileNameTemplate);
			String targetFileNames = targetFileNameTemplate.render();
			if(targetFileNames.replace(StringUtil.DEFAULT_JOIN_SEPARATOR,"").strip().isEmpty()) {
				continue;
			}
			String targetPath = targetPathTemplate.render();
			log.debug("render string {} to {}", templateName, targetPath);
			File targetFile = new File(tempSourceDir, targetPath);
			String targetFileName = targetFile.getName();
			List<String> targetFielNameLst = produceTargetFileName(targetFileName);
			if(targetFielNameLst.isEmpty()) {
				log.warn("target file name is empty: {}", targetFileName);
				continue;
			}
			FileUtils.forceMkdirParent(targetFile);
			for(String targetFileNameItem: targetFielNameLst) {
				Template template = TankTemplate.getInstance().getTemplate(templateName);
				bindTemplate(tankApplication, tankRoot, template);
				template.binding("fileName", targetFileNameItem);
				log.debug("rendering file {} to {}" , templateName, targetFileNameItem);
				template.renderTo(new FileOutputStream(new File(targetFile.getParent(), targetFileNameItem)));
			}
		}
			
		// copy temp source dir to out dir
		FileUtils.copyDirectory(tempSourceDir, outDir);
		log.debug("copy generated server source files to target dir:{}",  outDir);
		FileUtils.deleteQuietly(tempSourceDir);
	}
	
	public  void renderWeb(TankApplication tankApplication,TankRoot tankRoot, File outDir, boolean overwrite) throws IOException, URISyntaxException {
		// get all template files
		URL originTemplatesDir = ClassLoader.getSystemResource(TankTemplate.TANK_TEMPLATE_ROOT+"/"+WEB_TEMPLATE_ROOT);
		Path sourcePath = Path.of(originTemplatesDir.toURI());
		Collection<File> templateFiles = FileUtils.listFiles(sourcePath.toFile(), null, true);
		// create temp source dir
		File tempSourceDir = Files.createTempDirectory("temp_tank_web_source").toFile();
		tempSourceDir.deleteOnExit();
		// create target dir
		File targetDir = new File(outDir, WEB_ROOT);
		if(!targetDir.exists()) {
			targetDir.mkdirs();
		}
		// render
		for(File templateFile: templateFiles) {
			Path templatePath = sourcePath.relativize( templateFile.toPath());
			String templateName = WEB_TEMPLATE_ROOT+File.separator+templatePath.toString();
			boolean isTemplateFile = templateName.toLowerCase().endsWith(TEMPLATE_FILE_EXTENSION);
			Template targetPathTemplate = TankTemplate.getInstance().getStringTemplate(templateName);
			Template targetFileNameTemplate = TankTemplate.getInstance().getStringTemplate(templateFile.getName());
			if(targetPathTemplate == null || targetFileNameTemplate == null) {
				continue;
			}
			bindTemplate(tankApplication, tankRoot, targetPathTemplate, targetFileNameTemplate);
			String targetFileNames = targetFileNameTemplate.render();
			if(targetFileNames.replace(StringUtil.DEFAULT_JOIN_SEPARATOR,"").strip().isEmpty()) {
				continue;
			}
			String targetPath = targetPathTemplate.render();
			if(isTemplateFile) {
				targetPath = targetPath.substring(0, targetPath.length()-TEMPLATE_FILE_EXTENSION.length());
			}
			log.debug("render path {} to {}", templateName, targetPath);
			File targetFile = new File(tempSourceDir, targetPath);
			String targetFileName = targetFile.getName();
			List<String> targetFileNameLst = produceTargetFileName(targetFileName);
			if(targetFileNameLst.isEmpty()) {
				log.warn("target file name is empty: {}", targetFileName);
				continue;
			}
			FileUtils.forceMkdirParent(targetFile);
			for(String targetFileNameItem: targetFileNameLst) {
				File targetFileItem = new File(targetFile.getParent(), targetFileNameItem);
				log.debug("rendering file {} to {}" , templateName, targetFileItem);
				if(  !isTemplateFile) {
					FileUtils.copyFile(templateFile, targetFileItem);
					continue;
				}
				Template template = TankTemplate.getInstance().getTemplate(templateName);
				bindTemplate(tankApplication, tankRoot, template);
				template.binding("fileName", targetFileNameItem);
				template.renderTo(new FileOutputStream(targetFileItem));
			}
		}
		
		// copy temp source dir to out dir
		FileUtils.copyDirectory(new File(tempSourceDir, WEB_TEMPLATE_ROOT), targetDir);
		log.debug("copy generated web source files to target dir:{}",  targetDir);
		FileUtils.deleteQuietly(tempSourceDir);
	}
	
	private List<String> produceTargetFileName(String targetFileName) {
		
		if(StringUtils.isBlank(targetFileName)) {
			return Collections.emptyList();
		}
		List<String> result = new ArrayList<>();
		for(String item: targetFileName.strip().split(StringUtil.DEFAULT_JOIN_SEPARATOR)) {
			if(StringUtils.isBlank(item)) {
				continue;
			}
			result.add(item.strip());
		}
		return result;
	}
	

	private void bindTemplate(TankApplication tankApplication, TankRoot tankRoot, Template... templates) {
		if(templates == null || templates.length==0) {
			return;
		}
		for(Template template: templates) {
			template.binding("_root", tankApplication);
			String version = "1.0.0";
			if(tankRoot.getUpdatedTime() != null) {
				try {
					TemporalAccessor updatedTime = DateTimeFormatter.ofPattern(TankRoot.TIME_FORMAT).parse(tankRoot.getUpdatedTime());
					version = DateTimeFormatter.ofPattern(TankRoot.VERSION_FORMAT).format(updatedTime);
				}catch (Exception e) {
					log.error("generate version error", e);
				}
			}
			template.binding("version", version);
			template.binding("project", tankRoot); 
			template.binding("entities", tankRoot.getEntities()); 
			boolean dataRest = false;
			boolean mapStruct =false;
			for(TankEntity entity :tankRoot.getEntities()){
				if(!dataRest && TankEntity.ServiceType.RESOURCE.equals(entity.getServiceType())){
					dataRest = true;
				}
				if(!mapStruct && TankEntity.DTO.MAPSTRUCT.equals(entity.getDto()) ){
					mapStruct = true;
				}
				if(dataRest && mapStruct) {
					break;
				}
			}
			template.binding("dataRest", dataRest); 
			template.binding("packageNamePath", tankApplication.getPackageName().replace(".","/")); 
			template.binding("mapStruct", mapStruct); 
			template.binding("relationshipFields", ""); 
			template.binding("basicMapperClass", mapStruct?"BasicEntityMapper":""); 
			template.binding("enums", tankRoot.getEnums()); 
			template.binding("constants", tankRoot.getConstants()); 
			template.binding("relationships", tankRoot.getRelationships()); 
			
		}
	}

	protected void renderPomXml(TankApplication tankApplication, TankRoot tankRoot, OutputStream output) {
		try {
			Template pomTemplate = TankTemplate.getInstance().getTemplate(SERVER_TEMPLATE_ROOT+"/pom.xml");
			pomTemplate.binding("_root", tankApplication);
			pomTemplate.binding("dasherizedName", StringUtil.kebabCase(tankApplication.getName()));
			String humanizedName = StringUtil.lowerCase(tankApplication.getName());
			pomTemplate.binding("humanizedName", humanizedName);
			pomTemplate.binding("version", tankRoot.getUpdatedTime());
			pomTemplate.binding("projectDescription", String.format("Application \"%s\" of Project \"%s\"",humanizedName, tankRoot.getName() ));
			pomTemplate.renderTo(output);
		} catch (IOException e) {
			log.error("render pom.xml  error", e);
		}finally {
			if(output != null) {
				try {
					output.close();
				} catch (IOException e) {
					log.error("close pom.xml output stream error", e);
				}
			}
		}
	}

}
